﻿using WAU.Models.Framework;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using WAU.Models.CommodityModels;
using System.Collections.Concurrent;

namespace WAU.DAL
{
    /// <summary>
    /// CRUD 基本操作类（类型限制为基本类型）
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class BaseRepoMysql<T> where T : BaseModel
    {
        /// <summary>
        /// 锁对象
        /// </summary>
        private static object _lockObj = new object();

        public EssentialDbContext DbContext;
        public DbSet<T> EntitySet;

        public BaseRepoMysql(EssentialDbContext DbContext)
        {
            this.DbContext = DbContext;
            this.EntitySet = DbContext.Set<T>();
        }

        #region Create

        /// <summary>
        /// 添加
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public void Add(T entity)
        {
            DbContext.Set<T>().Add(entity);
        }

        /// <summary>
        /// 批量添加
        /// </summary>
        /// <param name="ts"></param>
        public void AddRange(IEnumerable<T> entities)
        {
            DbContext.Set<T>().AddRange(entities);
        }

        #endregion

        #region Retrieve

        /// <summary>
        /// 查询单个（跟踪）
        /// </summary>
        /// <param name="keyValues"></param>
        /// <returns></returns>
        public T Find(params object[] keyValues)
        {
            T t = DbContext.Set<T>().Find(keyValues);
            if (t != null)
            {
                return t;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 查询单个（异步、跟踪）
        /// </summary>
        /// <param name="keyValues"></param>
        /// <returns></returns>
        public async ValueTask<T> FindAsync(params object[] keyValues)
        {
            T t = await DbContext.Set<T>().FindAsync(keyValues);
            if (t != null)
            {
                return t;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 查询单个
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public T Find(Expression<Func<T, bool>> filter)
        {
            return DbContext.Set<T>().Where(filter).FirstOrDefault();
        }

        /// <summary>
        /// 查询单个（不跟踪实体）
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public T FindNoTracking(Expression<Func<T, bool>> filter)
        {
            return DbContext.Set<T>().Where(filter).AsNoTracking().FirstOrDefault();
        }

        /// <summary>
        /// 查询单个（异步）
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public async Task<T> FindAsync(Expression<Func<T, bool>> filter)
        {
            return await DbContext.Set<T>().Where(filter).FirstOrDefaultAsync();
        }

        /// <summary>
        /// 查询单个（异步、不跟踪实体）
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public async Task<T> FindAsyncNoTracking(Expression<Func<T, bool>> filter)
        {
            return await DbContext.Set<T>().Where(filter).AsNoTracking().FirstOrDefaultAsync();
        }

        /// <summary>
        /// 查询（返回 IQueryable）
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public IQueryable<T> Query(Expression<Func<T, bool>> filter)
        {
            return DbContext.Set<T>().Where(filter);
        }

        /// <summary>
        /// 查询（返回 IQueryable、不跟踪实体）
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public IQueryable<T> QueryNoTracking(Expression<Func<T, bool>> filter)
        {
            return DbContext.Set<T>().Where(filter).AsNoTracking();
        }

        /// <summary>
        /// 查询（返回 List）
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public List<T> List(Expression<Func<T, bool>> filter)
        {
            return DbContext.Set<T>().Where(filter).ToList();
        }

        /// <summary>
        /// 查询（异步、返回 List）
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public Task<List<T>> ListAsync(Expression<Func<T, bool>> filter)
        {
            return DbContext.Set<T>().Where(filter).ToListAsync();
        }

        /// <summary>
        /// 查询（返回 List、不跟踪实体）
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public List<T> ListNoTracking(Expression<Func<T, bool>> filter)
        {
            return DbContext.Set<T>().Where(filter).AsNoTracking().ToList();
        }

        /// <summary>
        /// 查询（异步、返回 List、不跟踪实体）
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public Task<List<T>> ListAsyncNoTracking(Expression<Func<T, bool>> filter)
        {
            return DbContext.Set<T>().Where(filter).AsNoTracking().ToListAsync();
        }

        /// <summary>
        /// 按页查询（返回IQueryable）
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="isPageable"></param>
        /// <param name="pageNumber"></param>
        /// <param name="pageSize"></param>
        /// <param name="ordering"></param>
        /// <param name="isAscending"></param>
        /// <returns></returns>
        public IQueryable<T> QueryByPage(Expression<Func<T, bool>> filter,
            bool isPageable, int pageNumber, int pageSize, Expression<Func<T, object>> ordering, bool isAscending)
        {
            IQueryable<T> query = DbContext.Set<T>().Where(filter);

            if (ordering == null)
            {
                ordering = x => x.CreateTime;
            }

            if (isAscending)
            {
                query = query.OrderBy(ordering).ThenBy(a => a.Id);
            }
            else
            {
                query = query.OrderByDescending(ordering).ThenBy(a => a.Id);
            }

            if (isPageable)
            {
                query = query.Skip((pageNumber - 1) * pageSize).Take(pageSize);
            }

            return query;
        }

        /// <summary>
        /// 按页查询（返回IQueryable）
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="ordering"></param>
        /// <param name="condition"></param>
        /// <returns></returns>
        public IQueryable<T> QueryByPage(Expression<Func<T, bool>> filter, Expression<Func<T, object>> ordering, BaseCondition condition)
        {
            return QueryByPage(filter, condition.IsPage, condition.PageIndex, condition.PageSize, ordering, condition.IsAsc);
        }

        /// <summary>
        /// 按页查询（返回结果）
        /// </summary>
        /// <param name="filter">查询条件</param>
        /// <param name="isPageable">是否分页</param>
        /// <param name="pageNumber">一页多少条</param>
        /// <param name="pageSize">第多少页</param>
        /// <param name="ordering">排序条件</param>
        /// <param name="isAscending">是否排序</param>
        /// <returns></returns>
        public QueryResult<T> QueryResultByPage(Expression<Func<T, bool>> filter,
            bool isPageable, int pageNumber, int pageSize, Expression<Func<T, object>> ordering, bool isAscending)
        {
            IQueryable<T> query = QueryByPage(filter, isPageable, pageNumber, pageSize, ordering, isAscending);
            int count = Count(filter);
            return new QueryResult<T>
            {
                DataList = query.ToList(),
                Total = count
            };
        }

        /// <summary>
        /// 按页查询（返回结果）
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="ordering"></param>
        /// <param name="condition"></param>
        /// <returns></returns>
        public QueryResult<T> QueryResultByPage(Expression<Func<T, bool>> filter, Expression<Func<T, object>> ordering, BaseCondition condition)
        {
            return QueryResultByPage(filter, condition.IsPage, condition.PageIndex, condition.PageSize, ordering, condition.IsAsc);
        }

        /// <summary>
        /// 按页查询（返回结果）（异步）
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="isPageable"></param>
        /// <param name="pageNumber"></param>
        /// <param name="pageSize"></param>
        /// <param name="ordering"></param>
        /// <param name="isAscending"></param>
        /// <returns></returns>
        public async Task<QueryResult<T>> QueryResultByPageAsync(Expression<Func<T, bool>> filter,
            bool isPageable, int pageNumber, int pageSize, Expression<Func<T, object>> ordering, bool isAscending)
        {
            IQueryable<T> query = QueryByPage(filter, isPageable, pageNumber, pageSize, ordering, isAscending);
            int count = await CountAsync(filter);
            return new QueryResult<T>
            {
                DataList = await query.ToListAsync(),
                Total = count
            };
        }

        /// <summary>
        /// 按页查询（返回结果）（异步）
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="ordering"></param>
        /// <param name="condition"></param>
        /// <returns></returns>
        public async Task<QueryResult<T>> QueryResultByPageAsync(Expression<Func<T, bool>> filter, Expression<Func<T, object>> ordering, BaseCondition condition)
        {
            return await QueryResultByPageAsync(filter, condition.IsPage, condition.PageIndex, condition.PageSize, ordering, condition.IsAsc);
        }

        /// <summary>
        /// 计数
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public int Count(Expression<Func<T, bool>> filter)
        {
            return DbContext.Set<T>().Count(filter);
        }

        /// <summary>
        /// 计数（异步）
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public async Task<int> CountAsync(Expression<Func<T, bool>> filter)
        {
            return await DbContext.Set<T>().CountAsync(filter);
        }

        #endregion

        #region Update

        /// <summary>
        /// 更新
        /// </summary>
        /// <param name="entity"></param>
        public void Update(T entity)
        {
            DbContext.Attach(entity);
            DbContext.Entry<T>(entity).State = EntityState.Modified;
        }

        /// <summary>
        /// 批量更新
        /// </summary>
        /// <param name="entities"></param>
        public void UpdateMany(IEnumerable<T> entities)
        {
            foreach (var entity in entities)
            {
                DbContext.Attach(entity);
                DbContext.Entry<T>(entity).State = EntityState.Modified;
            }
        }

        #endregion

        #region Delete

        /// <summary>
        /// 删除时使用的工具（存下两个需要使用的 MethodInfo）
        /// </summary>
        private class ToolForDelete
        {
            /// <summary>
            /// 要删除的类型
            /// </summary>
            public Type ToDeleteType { get; set; }

            /// <summary>
            /// 待删除类型对应的 DbContext.Set<?> 方法
            /// </summary>
            public MethodInfo DbContextSetMethod { get; set; }

            /// <summary>
            /// 待删除类型对应的 DbContext.Set<?>().Add 方法
            /// </summary>
            public MethodInfo CurrentTypeDbSetAddMethod { get; set; }
        }

        /// <summary>
        /// 删除时使用的工具字典
        /// </summary>
        private Dictionary<string, ToolForDelete> _cachedToolForDelete = new Dictionary<string, ToolForDelete>();

        /// <summary>
        /// 获取 删除实体类型 的全类名
        /// </summary>
        /// <returns></returns>
        private string GetToDeleteTypeFullName()
        {
            string fullName = typeof(T).FullName;
            string fullNamePrefix = fullName.Substring(0, fullName.LastIndexOf('.'));
            string simpleName = fullName.Substring(fullName.LastIndexOf('.') + 1);
            return $"{fullNamePrefix}.{BaseDeleteModel.DELETE_CLASS_PREFIX}{simpleName}";
        }

        /// <summary>
        /// 获取（和当前类型对应的）ToolForDelete 对象
        /// </summary>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private ToolForDelete GetToolForDelete(string toDeleteTypeFullName)
        {             
            if (_cachedToolForDelete.ContainsKey(toDeleteTypeFullName))
            {
                return _cachedToolForDelete[toDeleteTypeFullName];
            }

            lock (_lockObj)
            {
                if (_cachedToolForDelete.ContainsKey(toDeleteTypeFullName))
                {
                    return _cachedToolForDelete[toDeleteTypeFullName];
                }

                Assembly assembly = typeof(T).Assembly;
                Type toDeleteType = assembly.GetType(toDeleteTypeFullName);
                if (toDeleteType == null)
                {
                    throw new Exception($"删除实体时，未能找到 {toDeleteTypeFullName} 对应的类");
                }

                if(toDeleteType.BaseType.Name != nameof(BaseDeleteModel))
                {
                    throw new Exception($"{toDeleteTypeFullName} 必须继承自 ${nameof(BaseDeleteModel)}");
                }

                var setMethod = typeof(DbContext).GetMethod("Set", new Type[0]);
                var genericedSetMethod = setMethod.MakeGenericMethod(toDeleteType);

                var dbSetType = typeof(DbSet<>).MakeGenericType(toDeleteType);
                var dbSetAddMethod = dbSetType.GetMethod("Add", new Type[] { toDeleteType });

                ToolForDelete tfd = new ToolForDelete
                {
                    ToDeleteType = toDeleteType,
                    DbContextSetMethod = genericedSetMethod,
                    CurrentTypeDbSetAddMethod = dbSetAddMethod
                };
                _cachedToolForDelete.Add(toDeleteTypeFullName, tfd);

                return tfd;
            }
        }

        /// <summary>
        /// 获取转换后的删除实体
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="toDeleteType"></param>
        /// <returns></returns>
        private BaseDeleteModel GetToDeleteEntity(T entity, Type toDeleteType)
        {
            // 逐个属性拷贝
            var toDeleteIns = typeof(T).Assembly.CreateInstance(toDeleteType.FullName);
            foreach (var propertyInfo in typeof(T).GetProperties())
            {
                PropertyInfo pi = toDeleteType.GetProperty(propertyInfo.Name);
                pi.SetValue(toDeleteIns, propertyInfo.GetValue(entity));
            }

            // 转为父类 BaseDeleteModel
            return (BaseDeleteModel) toDeleteIns;
        }

        /// <summary>
        /// 向对应的 DbSet 添加 删除实体
        /// </summary>
        /// <param name="deleteBy"></param>
        /// <param name="entity"></param>
        private void AddDeleteEntity(string deleteBy, T entity)
        {
            string toDeleteTypeFullName = GetToDeleteTypeFullName();
            ToolForDelete tfd = GetToolForDelete(toDeleteTypeFullName);
            var toDeleteModel = GetToDeleteEntity(entity, tfd.ToDeleteType);
            var dbSetObj = tfd.DbContextSetMethod.Invoke(DbContext, null);

            toDeleteModel.DeleteBy = deleteBy;
            toDeleteModel.DeleteTime = DateTime.Now;
            
            tfd.CurrentTypeDbSetAddMethod.Invoke(dbSetObj, new object[] { toDeleteModel });            
        }

        /// <summary>
        /// 向对应的 DbSet 添加 删除实体（多个）
        /// </summary>
        /// <param name="deleteBy"></param>
        /// <param name="entities"></param>
        private void AddDeleteEntities(string deleteBy, IEnumerable<T> entities)
        {
            string toDeleteTypeFullName = GetToDeleteTypeFullName();
            ToolForDelete tfd = GetToolForDelete(toDeleteTypeFullName);
            var dbSetObj = tfd.DbContextSetMethod.Invoke(DbContext, null);
            foreach (var entity in entities)
            {
                var toDeleteModel = GetToDeleteEntity(entity, tfd.ToDeleteType);
                toDeleteModel.DeleteBy = deleteBy;
                toDeleteModel.DeleteTime = DateTime.Now;
                
                tfd.CurrentTypeDbSetAddMethod.Invoke(dbSetObj, new object[] { toDeleteModel });
            }           
        }

        /// <summary>
        /// 删除（历史表记录）
        /// </summary>
        /// <param name="currentUserId"></param>
        /// <param name="id"></param>
        public void Delete(string currentUserId, string id)
        {
            T entity = EntitySet.FirstOrDefault(a => a.Id == id);
            DbContext.Set<T>().Remove(entity);
            AddDeleteEntity(currentUserId, entity);
        }

        /// <summary>
        /// 删除（历史表记录）
        /// </summary>
        /// <param name="currentUserId"></param>
        /// <param name="entity"></param>
        public void Delete(string currentUserId, T entity)
        {
            DbContext.Set<T>().Remove(entity);
            AddDeleteEntity(currentUserId, entity);
        }

        /// <summary>
        /// 批量删除（历史表记录）
        /// </summary>
        /// <param name="currentUserId"></param>
        /// <param name="entities"></param>
        public void DeleteMany(string currentUserId, string[] ids)
        {
            List<T> entities = EntitySet.Where(a => ids.Contains((a as BaseModel).Id)).ToList();

            DbContext.Set<T>().RemoveRange(entities);
            AddDeleteEntities(currentUserId, entities);
        }

        /// <summary>
        /// 批量删除（历史表记录）
        /// </summary>
        /// <param name="currentUserId"></param>
        /// <param name="entities"></param>
        public void DeleteMany(string currentUserId, IEnumerable<T> entities)
        {
            DbContext.Set<T>().RemoveRange(entities);
            AddDeleteEntities(currentUserId, entities);
        }
        
        #endregion

        #region Save

        /// <summary>
        /// 保存
        /// </summary>
        /// <returns></returns>
        public int SaveChanges()
        {
            return DbContext.SaveChanges();
        }

        /// <summary>
        /// 保存（异步）
        /// </summary>
        /// <returns></returns>
        public async Task<int> SaveChangesAsync()
        {
            return await DbContext.SaveChangesAsync();
        }

        #endregion
    }

    /// <summary>
    /// CRUD 基本操作类（类型无限制，便于关联后的结果集使用）
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class RepoMySql
    {
        #region Retrieve

        /// <summary>
        /// 按页查询（返回IQueryable）
        /// </summary>
        /// <param name="query"></param>
        /// <param name="isPageable"></param>
        /// <param name="pageNumber"></param>
        /// <param name="pageSize"></param>
        /// <param name="ordering"></param>
        /// <param name="isAscending"></param>
        /// <param name="reserveOrdering">备用排序</param>
        /// <param name="reserveIsAscending">备用排序顺序</param>
        /// <returns></returns>
        public IQueryable<T> QueryByPage<T>(IQueryable<T> query,
            bool isPageable, int pageNumber, int pageSize, 
            Expression<Func<T, object>> ordering, bool isAscending,
            Expression<Func<T, object>> reserveOrdering = null, bool? reserveIsAscending = null)
        {
            if (ordering != null)
            {
                if (isAscending)
                {
                    query = query.OrderBy(ordering);
                }
                else
                {
                    query = query.OrderByDescending(ordering);
                }

                if(reserveOrdering != null && reserveIsAscending != null)
                {
                    if (reserveIsAscending.Value)
                    {
                        query = ((IOrderedQueryable<T>)query).ThenBy(ordering);
                    }
                    else
                    {
                        query = ((IOrderedQueryable<T>)query).ThenByDescending(ordering);
                    }
                }
            }

            if (isPageable)
            {
                query = query.Skip((pageNumber - 1) * pageSize).Take(pageSize);
            }

            return query;
        }

        /// <summary>
        /// 按页查询（返回IQueryable）
        /// </summary>
        /// <param name="query"></param>
        /// <param name="isPageable"></param>
        /// <param name="pageNumber"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public IQueryable<T> QueryByPage<T>(IQueryable<T> query, bool isPageable, int pageNumber, int pageSize)
        {
            if (isPageable)
            {
                query = query.Skip((pageNumber - 1) * pageSize).Take(pageSize);
            }

            return query;
        }

        /// <summary>
        /// 按页查询（返回结果）
        /// </summary>
        /// <param name="query"></param>
        /// <param name="isPageable"></param>
        /// <param name="pageNumber"></param>
        /// <param name="pageSize"></param>
        /// <param name="ordering"></param>
        /// <param name="isAscending"></param>
        /// <returns></returns>
        public QueryResult<T> QueryResultByPage<T>(IQueryable<T> query,
            bool isPageable, int pageNumber, int pageSize, Expression<Func<T, object>> ordering, bool isAscending)
        {
            int count = Count(query);
            query = QueryByPage(query, isPageable, pageNumber, pageSize, ordering, isAscending);
            return new QueryResult<T>
            {
                DataList = query.ToList(),
                Total = count
            };
        }

        /// <summary>
        /// 按页查询（返回结果）
        /// </summary>
        /// <param name="query"></param>
        /// <param name="ordering"></param>
        /// <param name="condition"></param>
        /// <returns></returns>
        public QueryResult<T> QueryResultByPage<T>(IQueryable<T> query, Expression<Func<T, object>> ordering, BaseCondition condition)
        {
            return QueryResultByPage(query, condition.IsPage, condition.PageIndex, condition.PageSize, ordering, condition.IsAsc);
        }

        /// <summary>
        /// 按页查询（返回结果）（异步）
        /// </summary>
        /// <param name="isPageable"></param>
        /// <param name="pageNumber"></param>
        /// <param name="pageSize"></param>
        /// <param name="ordering"></param>
        /// <param name="isAscending"></param>
        /// <param name="reserveOrdering">备用排序</param>
        /// <param name="reserveIsAscending">备用排序顺序</param>
        /// <returns></returns>
        public async Task<QueryResult<T>> QueryResultByPageAsync<T>(IQueryable<T> query,
            bool isPageable, int pageNumber, int pageSize, 
            Expression<Func<T, object>> ordering, bool isAscending,
            Expression<Func<T, object>> reserveOrdering = null, bool? reserveIsAscending = null)
        {
            int count = await CountAsync(query);
            query = QueryByPage(query, isPageable, pageNumber, pageSize, 
                        ordering, isAscending, reserveOrdering, reserveIsAscending);
            return new QueryResult<T>
            {
                DataList = await query.ToListAsync(),
                Total = count
            };
        }

        /// <summary>
        /// 按页查询（返回结果）（异步）
        /// </summary>
        /// <param name="isPageable"></param>
        /// <param name="pageNumber"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public async Task<QueryResult<T>> QueryResultByPageAsync<T>(IQueryable<T> query, bool isPageable, int pageNumber, int pageSize)
        {
            int count = await CountAsync(query);
            query = QueryByPage(query, isPageable, pageNumber, pageSize);
            return new QueryResult<T>
            {
                DataList = await query.ToListAsync(),
                Total = count
            };
        }

        /// <summary>
        /// 按页查询（返回结果）（异步）
        /// </summary>
        /// <param name="query"></param>
        /// <param name="ordering"></param>
        /// <param name="condition"></param>
        /// <param name="reserveOrdering">备用排序</param>
        /// <param name="reserveIsAscending">备用排序顺序</param>
        /// <returns></returns>
        public async Task<QueryResult<T>> QueryResultByPageAsync<T>(IQueryable<T> query, 
            Expression<Func<T, object>> ordering, BaseCondition condition,
            Expression<Func<T, object>> reserveOrdering = null, bool? reserveIsAscending = null)
        {
            return await QueryResultByPageAsync(query, condition.IsPage, condition.PageIndex, condition.PageSize, 
                            ordering, condition.IsAsc, reserveOrdering, reserveIsAscending);
        }

        /// <summary>
        /// 按页查询（返回结果）（异步）- 需要在 <paramref name="query"/> 中包含排序操作
        /// </summary>
        /// <param name="query"></param>
        /// <param name="condition"></param>
        /// <returns></returns>
        public async Task<QueryResult<T>> QueryResultByPageAsync<T>(IQueryable<T> query, BaseCondition condition)
        {
            return await QueryResultByPageAsync(query, condition.IsPage, condition.PageIndex, condition.PageSize);
        }

        /// <summary>
        /// 计数
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public int Count<T>(IQueryable<T> query)
        {
            return query.Count();
        }

        /// <summary>
        /// 计数（异步）
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public async Task<int> CountAsync<T>(IQueryable<T> query)
        {
            return await query.CountAsync();
        }

        #endregion
    }
}
